#define TELNETSESSION__SELFPULSING
#define LOG_IAC

using System;
using System.Collections.Generic;

namespace SharpMud.Net.Telnet.Session
{
	/// <summary>
	/// This class is the recommended base type for all Telnet Sessions. It provides thorough, multi-protocol parsing 
	/// and management.
	/// </summary>
	public class TelnetSessionBase : SharpMud.Net.IHostedConnectionHandler, SharpMud.Terminal.IInteractiveTerminal, IDisposable
#if ! TELNETSESSION__SELFPULSING
		, System.Threading.IRequiresExternalPulsing
#endif
	{
		#region Private Variables		
        private SharpMud.Server.Systems.Net.INetworkSystem _Host;
        private SharpMud.Net.INetworkConnection		_Connection;
        private log4net.ILog                        _Log;
		protected System.IO.ParsedStream			_ParsedConnectionStream;
		protected System.IO.StreamReader			_ParsedConnectionReader;
		protected System.IO.StreamWriter			_ParsedConnectionWriter;
		protected System.IO.LatentStreamReader		_LatentConnectionReader;
		protected bool								_UseMXP;
		protected bool								_UseANSI;

		private string								_SessionID;

		//Processors
		private SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACTelnetLayer	  _IAC;
		private SharpMud.Net.Telnet.Processors.TextFormatting.TextFormattingLayer _TextFormatting;
		internal SharpMud.Net.Telnet.Session.DataProcessors.MCCP.CompressionLayer	_OutboundCompression;
		private SharpMud.Net.Telnet.Session.DataProcessors.DataThroughputTracker _InputThroughputTracker;
		private SharpMud.Net.Telnet.Session.DataProcessors.DataThroughputTracker _OutputThroughputTracker;

		//Agents
		private StandardAgentsPlatform	_Agents;

		//Other
		private PromptTerminationPreference									_PromptTerminationPreference;
		#endregion

		/// <summary>
		/// This method is used to create a new session ID for new sessions.
		/// </summary>
		private static string GenerateNewSessionID()
		{
			//Setup a session ID. Random hex number, from a Guid.
			//Don't use the whole guid, way friggin too long.
			string s = String.Format("{0:N}", Guid.NewGuid());
			s = s.Substring(0,s.Length / 4);
			return s;
		}

		#region Public Properties
		/// <summary>
		/// This property gets the unique session ID for this session.
		/// Basically a highly truncated GUID.
		/// </summary>
		public string SessionID
		{
			get
			{
				return this._SessionID;
			}
		}
		/// <summary>
		/// This property gets the IAC layer for this telnet session.
		/// </summary>
		public SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACTelnetLayer IAC
		{
			get
			{
				return _IAC;
			}
		}
		/// <summary>
		/// This property gets the NetworkConnection that this session is in charge of.
		/// </summary>
		public SharpMud.Net.INetworkConnection								Connection
		{
			get
			{
				return this._Connection;
			}
		}
		/// <summary>
		/// This property gets a LatentStreamReader that can be used to read text from the incoming
		/// data, causing the current thread to wait if the requested amount of data is not available.
		/// </summary>
		public System.IO.LatentStreamReader									LatentIn
		{
			get
			{
				return this._LatentConnectionReader;
			}
		}

		/// <summary>
		/// Gets the client terminal type detected, if any.
		/// </summary>
		public string TerminalType
		{
			get
			{
				if(this.Agents.TerminalType.CurrentTerminalType==String.Empty)
					return String.Empty;
				else
					return this.Agents.TerminalType.CurrentTerminalType;
			}
		}

		public long TotalBytesOut
		{
			get
			{
				return this._OutputThroughputTracker.TotalThroughput;
			}
		}
		public System.DateTime LastOutputTime
		{
			get
			{
				return this._OutputThroughputTracker.LastThroughputTime;
			}
		}
		public long TotalBytesIn
		{
			get
			{
				return this._InputThroughputTracker.TotalThroughput;
			}
		}
		public System.DateTime LastInputTime
		{
			get
			{
				return this._InputThroughputTracker.LastThroughputTime;
			}
		}
		public bool UseANSI
		{
			get
			{
				return _UseANSI;
			}
			set
			{
				_UseANSI = value;
			}
		}
		public bool UseMXP
		{
			get
			{
				return _UseMXP;
			}
			set
			{
				_UseMXP = value;
			}
		}
		#endregion
		
		#region Constructors
		/// <summary>
		/// The default public constructor for TelnetSessionBase.
		/// This method performs NO initialization. 
		/// See the implementation of IConnectionHandler.Initialize()
		/// </summary>
		public TelnetSessionBase()
		{
            this._SessionID = GenerateNewSessionID();
            _Log = log4net.LogManager.GetLogger("SharpMud.Net.Telnet.TelnetSession[" + this._SessionID + "]");

			//No initialization done here. See IConnectionHandler.Initialize()	
		}

        public log4net.ILog Log
        {
            get
            {
                return _Log;
            }
        }
		#endregion

		#region Parsed IO
		/// <summary>
		/// Gets the ParsedStream that provides In-The-Box parsing for multi-protocol Telnet I/O on this session.
		/// </summary>
		public System.IO.Stream Stream
		{
			get
			{
				return (System.IO.Stream)this._ParsedConnectionStream;
			}
		}
		/// <summary>
		/// Gets the ParsedStreamReader that provides In-The-Box parsing for multi-protocol Telnet input on this session.
		/// </summary>
		public System.IO.TextReader In
		{
			get
			{
				return (System.IO.TextReader)this._ParsedConnectionReader;
			}
		}
		/// <summary>
		/// Gets the ParsedStreamReader that provides In-The-Box parsing for multi-protocol Telnet output on this session.
		/// </summary>
		public System.IO.TextWriter Out
		{
			get
			{
				return (System.IO.TextWriter)this._ParsedConnectionWriter;
			}
		}
		#endregion

		#region Write
		#region Prompt
		/// <summary>
		/// Sends a prompt to the client, trying as hard as possible to make sure the client treats it
		/// as a prompt. (Using either Telnet-GA, or Telnet-EOR, etc.)
		/// </summary>
		/// <param name="prompt">The prompt to send to the client</param>
		public void WritePrompt(string prompt)
		{
			try
			{
				this.Out.Write(this.NewLine);
				this.Out.Write(prompt);
				this.Out.Flush();
				if(this._PromptTerminationPreference==PromptTerminationPreference.GoAhead && this.Agents.GoAhead.Enabled)
				{
					this.IAC.Out.Send_Other_Packet(SharpMud.Net.Telnet.IAC.ControlCodes.GA);
					this.Out.Flush();
				}
				else if(this._PromptTerminationPreference==PromptTerminationPreference.EndOfRecord && this.Agents.EndOfRecord.Enabled)
				{
					this.IAC.Out.Send_Other_Packet(SharpMud.Net.Telnet.IAC.ControlCodes.TELOPT.EOR);
					this.Out.Flush();
				}
				else if(this._PromptTerminationPreference==PromptTerminationPreference.NoTerminator)
				{
					//Nothing specific to do
				}
				else
				{
					//Ultimate default == PromptTerminationPreference.DelayedNewline
					bool old_autoflush = ((System.IO.StreamWriter)this.Out).AutoFlush;
					((System.IO.StreamWriter)this.Out).AutoFlush = false;
					this.Out.Write(this.NewLine);
					((System.IO.StreamWriter)this.Out).AutoFlush = old_autoflush;
				}
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WritePrompt(string format, object arg0)
		{
			this.WritePrompt(String.Format(format,arg0));
		}
		public void WritePrompt(string format, object arg0, object arg1)
		{
			this.WritePrompt(String.Format(format,arg0,arg1));
		}
		public void WritePrompt(string format, object arg0, object arg1, object arg2)
		{
			this.WritePrompt(String.Format(format,arg0,arg1,arg2));
		}
		/*
		public void WritePrompt(string format, params object[] arg)
		{
			this.WritePrompt(String.Format(format,arg));
		}
		*/
		#endregion
		public void Write(char[] buffer)
		{ 
			try
			{
				this.Out.Write(buffer);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(bool value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(char value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(decimal value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(double value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(int value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(long value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(object value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(float value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(string value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		[CLSCompliant(false)]
		public void Write(uint value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		[CLSCompliant(false)]
		public void Write(ulong value)
		{ 
			try
			{
				this.Out.Write(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(string format, object arg0)
		{ 
			try
			{
				this.Out.Write(format,arg0);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		/*
		public void Write(string format, params object[] arg)
		{
			try
			{
				this.Out.Write(format,arg);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		*/
		public void Write(char[] buffer, int index, int count)
		{
			try
			{
				this.Out.Write(buffer,index,count);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(string format, object arg0, object arg1)
		{
			try
			{
				this.Out.Write(format,arg0,arg1);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Write(string format, object arg0, object arg1, object arg2)
		{
			try
			{
				this.Out.Write(format,arg0,arg1,arg2);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine()
		{
			try
			{
				this.Out.WriteLine();
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(char value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(char[] buffer)
		{ 
			try
			{
				this.Out.WriteLine(buffer);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(bool value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(decimal value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(double value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(int value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(long value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(object value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(float value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(string value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		[CLSCompliant(false)]
		public void WriteLine(uint value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		[CLSCompliant(false)]
		public void WriteLine(ulong value)
		{ 
			try
			{
				this.Out.WriteLine(value);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(string format, object arg0)
		{ 
			try
			{
				this.Out.WriteLine(format,arg0);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		/*
		public void WriteLine(string format, params object[] arg)
		{ 
			try
			{
				this.Out.WriteLine(format,arg);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		*/
		public void WriteLine(string format, object arg0, object arg1)
		{ 
			try
			{
				this.Out.WriteLine(format,arg0,arg1);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(char[] buffer, int index, int count)
		{ 
			try
			{
				this.Out.WriteLine(buffer,index,count);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void WriteLine(string format, object arg0, object arg1, object arg2)
		{ 
			try
			{
				this.Out.WriteLine(format,arg0,arg1,arg2);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		public void Flush()
		{
			try
			{
				this.Out.Flush();
                this._ParsedConnectionStream.Flush();
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
		}
		#endregion

		#region Read
		#region Prompt

		/// <summary>
		/// Sends a prompt to the client, trying as hard as possible to make sure the client treats it
		/// as a prompt. (Using either Telnet-GA, or Telnet-EOR, etc.).
		/// Then it waits for a line of text as a response.
		/// </summary>
		/// <param name="prompt">The prompt to send to the client</param>
		public string Prompt(string prompt)
		{
            string rslt = null;
			try
			{
                WritePrompt(prompt);
			}
			catch(System.IO.IOException e)
			{
				this.RegisterConnectionFailed();
			}
            rslt = this.LatentIn.ReadLine();
			return rslt;
		}
		/// <summary>
		/// Sends a prompt to the client, trying as hard as possible to make sure the client treats it
		/// as a prompt. (Using either Telnet-GA, or Telnet-EOR, etc.).
		/// Then it waits for a line of text as a response.
		/// </summary>
		public string Prompt(string format, object arg0)
		{
			return this.Prompt(String.Format(format,arg0));
		}
		/// <summary>
		/// Sends a prompt to the client, trying as hard as possible to make sure the client treats it
		/// as a prompt. (Using either Telnet-GA, or Telnet-EOR, etc.).
		/// Then it waits for a line of text as a response.
		/// </summary>
		public string Prompt(string format, object arg0, object arg1)
		{
			return this.Prompt(String.Format(format,arg0,arg1));
		}
		/// <summary>
		/// Sends a prompt to the client, trying as hard as possible to make sure the client treats it
		/// as a prompt. (Using either Telnet-GA, or Telnet-EOR, etc.).
		/// Then it waits for a line of text as a response.
		/// </summary>
		public string Prompt(string format, object arg0, object arg1, object arg2)
		{
			return this.Prompt(String.Format(format,arg0,arg1,arg2));
		}
		/*
		public string Prompt(string format, params object[] arg)
		{
			return this.Prompt(String.Format(format,arg));
		}
		*/
		#endregion
		/// <summary>
		/// Reads a byte from the underlying connection.
		/// </summary>
		/// <param name="wait">Whether or not the current thread should be halted in order to wait for
		/// an incoming byte should not one have already been recieved</param>
		/// <returns>If a byte was read, it is returned. If no byte was available, -1 is returned.</returns>
		public int ReadByte(bool wait)
		{
			if(wait==true)
				return this.LatentIn.Read();
			else
				return this.In.Read();
		}
		/// <summary>
		/// Reads a character from the underlyng connection.
		/// </summary>
		/// <param name="wait">Whether or not the current thread should be halted in order to wait for
		/// an incoming character should not one have already been receieved</param>
		/// <returns>If a char was read, it is returned. If no char was read, null is returned.</returns>
		public char ReadChar(bool wait)
		{
			if(wait==true)
				return System.Convert.ToChar((byte)this.ReadByte(wait/*true*/));
			else
			{
				int x = this.ReadByte(wait/*false*/);
				if(x<0) 
					return Convert.ToChar(null);
				else
					return System.Convert.ToChar((byte)x);
			}
		}
		/// <summary>
		/// Reads a line of text from the underlying connection.
		/// </summary>
		/// <param name="wait">Whether or not the current thread should be halted in order to wait for
		/// an incoming line of text should not one have already been receieved</param>
		/// <returns>Returns either a full line of text, or as much text that could be read from the connection.</returns>
		public string ReadLine(bool wait)
		{
			if(wait)
				return this.LatentIn.ReadLine();
			else
				return this.In.ReadLine();
		}
		/// <summary>
		/// Reads all of the already-recieved text from the underlying connection.
		/// </summary>
		/// <returns>Returns all the text from the underlying connection that has already been recieved</returns>
		public string ReadToEnd()
		{
			return this.In.ReadToEnd();
		}
		#endregion

		#region IConnectionHandler Members

		/// <summary>
		/// This function sets up the input/output processing layers for the telnet connection
		/// </summary>
		private void _SetupLayers()
		{
            try
            {
                _OutboundCompression = new SharpMud.Net.Telnet.Session.DataProcessors.MCCP.CompressionLayer();
                _IAC = new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACTelnetLayer(this);
                _TextFormatting = new Processors.TextFormatting.TextFormattingLayer(this);
                _InputThroughputTracker = new DataProcessors.DataThroughputTracker();
                _OutputThroughputTracker = new DataProcessors.DataThroughputTracker();

                System.IO.IDataProcessor[] inputParsers = new System.IO.IDataProcessor[] { _InputThroughputTracker, IAC.In };
                System.IO.IDataProcessor[] outputParsers = new System.IO.IDataProcessor[] { (System.IO.IDataProcessor)_OutboundCompression, _OutputThroughputTracker, IAC.Out, _TextFormatting.Out };

                this._ParsedConnectionStream = new System.IO.ParsedStream(this._Connection.Stream, inputParsers, outputParsers);

                this._ParsedConnectionReader = new System.IO.StreamReader(this._ParsedConnectionStream, System.Text.Encoding.ASCII);
                this._ParsedConnectionWriter = new System.IO.StreamWriter(this._ParsedConnectionStream);
                this._LatentConnectionReader = new System.IO.LatentStreamReader(this._ParsedConnectionStream, this._ParsedConnectionReader.CurrentEncoding);

                this._ParsedConnectionWriter.AutoFlush = true;
            }
            catch (System.IO.IOException e)
            {
                this.RegisterConnectionFailed();
            }
		}

		protected virtual StandardAgentsPlatform _SetupAgents()
		{
			return new StandardAgentsPlatform(this);
		}

		private void _SetupPreferenceDefaults()
		{
			_PromptTerminationPreference = PromptTerminationPreference.EndOfRecord;
            _UseANSI = true;
            _UseMXP = false;
		}

		/// <summary>
		/// This method, when overidden by a derived class, allows the base to tell the derived class
		/// to do any extra initialization that is neccesary.
		/// The base implementation does nothing, and is called externally.
		/// </summary>
		protected virtual void _Initialize() {}
		#endregion

		#region IRequiresExternalPulsing Members

		/// <summary>
		/// This method is called to tell the TelnetSession that it needs to update itself and do
		/// any waiting work.
		/// </summary>
#if TELNETSESSION__SELFPULSING
		protected void Pulse()
#else
		public void Pulse()
#endif
		{
			this._Connection.Pulse();
			this._Pulse();
			return;
		}
		
		/// <summary>
		/// This method, when overidden by a derived class, allows the derived class to be instructed
		/// when to update itself.
		/// The base implementation does nothing, and is called externally.
		/// </summary>
		protected virtual void _Pulse()
		{
		}

		#endregion

		#region Self Pulsing
#if TELNETSESSION__SELFPULSING	
		#region Private Variables
		private System.Threading.Thread _Thread;

		private bool _ThreadShouldBeAlive;
		#endregion

		/// <summary>
		/// This method performs the waiting that this TelnetSession needs to do between pulses.
		/// </summary>
		private void DoPulseLoopWait()
		{
			System.Threading.Thread.Sleep(GetPulseLoopInterval());
		}
		/// <summary>
		/// This method returns the amount of time to wait between this TelnetSession's pulses.
		/// </summary>
		/// <returns></returns>
		protected virtual System.TimeSpan GetPulseLoopInterval()
		{
			return System.TimeSpan.FromSeconds(0.05);
		}
		/// <summary>
		/// This is the thread start for this self-pulsing TelnetSession
		/// </summary>
		private void WorkThreadStart()
		{
			Log.Info("Pulsar thread RUNNING.");

			try
			{
				System.Threading.Thread.CurrentThread.IsBackground = true;
				do
				{
					DoPulseLoopWait();
					this.Pulse();
				} while(this._ThreadShouldBeAlive);
			}
			catch(System.Threading.ThreadAbortException tae)
			{
				this._ThreadShouldBeAlive = false;
                Log.Info("Pulsar thread externally TERMINATED.");
				return;
			}

            Log.Info("Pulsar thread self-TERMINATED.");
		}
		/// <summary>
		/// Gets the thread that drives this self-pulsing TelnetSession.
		/// </summary>
		public System.Threading.Thread Thread
		{
			get
			{
				return _Thread;
			}
		}
#endif
		#endregion

		/// <summary>
		/// Gets the newline to be used against the session client
		/// </summary>
		public string NewLine
		{
			get
			{
				return System.Environment.NewLine;
			}
		}

		/// <summary>
		/// This property is just a nice, friendly-named container for some agents that perform
		/// friendly, front-end tasks using low-level telnet protocols.
		/// </summary>
		public StandardAgentsPlatform Agents
		{
			get
			{
				return this._Agents;
			}
		}

		protected void RegisterConnectionFailed()
		{
			if(this.Connection.Status==SharpMud.Net.ConnectionStatus.Disconnected)
				return;
            Log.Warn("Connection Failed. Closing session...");
            _ThreadShouldBeAlive = false;
            this.Connection.Close();   
		}
        protected void RegisterConnectionFailed(System.Exception e)
        {
            if (this.Connection.Status == SharpMud.Net.ConnectionStatus.Disconnected)
                return;
            Log.Error("{" + e.GetType().Name + "} Closing session...");
            _ThreadShouldBeAlive = false;
            this.Connection.Close();
        }

		#region Event Handlers
		/// <summary>
		/// This method is called when this TelnetSession's connection is detected to be 'Closing'.
		/// </summary>
		private void Connection_Closing(SharpMud.Net.INetworkConnection sender, SharpMud.Net.ConnectionEventArgs e)
		{
#if TELNETSESSION__SELFPULSING
			this._ThreadShouldBeAlive = false;
#endif
		}
		/// <summary>
		/// This method is called when this TelnetSession's connection is detected to be 'LinkLost'.
		/// </summary>
		private void Connection_LinkLost(SharpMud.Net.INetworkConnection sender, SharpMud.Net.ConnectionEventArgs e)
		{
#if TELNETSESSION__SELFPULSING
			this._ThreadShouldBeAlive = false;
#endif
		}
		/// <summary>
		/// This method is called when this TelnetSession's connection is detected to be 'Disconnected'.
		/// </summary>
		private void Connection_Disconnected(SharpMud.Net.INetworkConnection sender, SharpMud.Net.ConnectionEventArgs e)
		{
#if TELNETSESSION__SELFPULSING
			this._ThreadShouldBeAlive = false;
#endif
		}
		#endregion

#if LOG_IAC
		private void In_IAC_Feature_Negotiation(SharpMud.Net.Telnet.Session.DataProcessors.IAC.InputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventArgs e)
		{
            Log.Debug("[IN]  IAC " + e.NegotiationQuery.Name + " " + e.FeatureType.Name);
		}

		private void In_IAC_Other(SharpMud.Net.Telnet.Session.DataProcessors.IAC.InputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventArgs e)
		{
            Log.Debug("[IN]  IAC " + ((Telnet.IAC.ControlCode)e.Other).Name);
		}

		private void In_IAC_Subrequest(SharpMud.Net.Telnet.Session.DataProcessors.IAC.InputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventArgs e)
		{
            Log.Debug("[IN]  IAC " + "SB " + e.FeatureType.Name + " {" + e.SubrequestData.Length.ToString() + " Bytes} IAC SE");
		}

		private void Out_IAC_Feature_Negotiation(SharpMud.Net.Telnet.Session.DataProcessors.IAC.OutputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventArgs e)
		{
            Log.Debug("[OUT] IAC " + e.NegotiationQuery.Name + " " + e.FeatureType.Name);
		}

		private void Out_IAC_Other(SharpMud.Net.Telnet.Session.DataProcessors.IAC.OutputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventArgs e)
		{
            Log.Debug("[OUT] IAC " + ((Telnet.IAC.ControlCode)e.Other).Name);
		}

		private void Out_IAC_Subrequest(SharpMud.Net.Telnet.Session.DataProcessors.IAC.OutputLayer sender, SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventArgs e)
		{
            Log.Debug("[OUT] IAC " + "SB " + e.FeatureType.Name + " {" + e.SubrequestData.Length.ToString() + " Bytes} IAC SE");
		}
#endif

        #region IInteractiveTerminal Members

        char SharpMud.Terminal.IInteractiveTerminal.Read()
        {
            return this.ReadChar(true);
        }

        int SharpMud.Terminal.IInteractiveTerminal.Peek()
        {
            char c = this.ReadChar(false);
            if(c=='\0')
                return -1;
            else
                return Convert.ToInt32(c);
        }

        string SharpMud.Terminal.IInteractiveTerminal.ReadLine()
        {
            return this.ReadLine(true);
        }

        System.Text.Encoding SharpMud.Terminal.IInteractiveTerminal.InputEncoding
        {
            get
            {
                return this.LatentIn.CurrentEncoding;
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        #endregion

        #region IOutputTerminal Members

        public void Beep()
        {
            this.Write('\x7');
        }

        public void CLS()
        {
            ((SharpMud.Terminal.IOutputTerminal)this).Clear();
        }

        void SharpMud.Terminal.IOutputTerminal.Clear()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void ResetColor()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void SharpMud.Terminal.IOutputTerminal.Write(string value)
        {
            this.Write(value);
        }

        void SharpMud.Terminal.IOutputTerminal.Write(char value)
        {
            this.Write(value);
        }

        void SharpMud.Terminal.IOutputTerminal.Write(char[] buffer)
        {
            this.Write(buffer);
        }

        void SharpMud.Terminal.IOutputTerminal.Write(char[] buffer, int start, int length)
        {
            this.Write(buffer, start, length);
        }

        void SharpMud.Terminal.IOutputTerminal.WriteLine(char value)
        {
            this.WriteLine(value);
        }

        void SharpMud.Terminal.IOutputTerminal.WriteLine(string value)
        {
            this.WriteLine(value);
        }

        void SharpMud.Terminal.IOutputTerminal.WriteLine(char[] buffer)
        {
            this.WriteLine(buffer);
        }

        void SharpMud.Terminal.IOutputTerminal.WriteLine(char[] buffer, int start, int length)
        {
            this.WriteLine(buffer, start, length);
        }

        void SharpMud.Terminal.IOutputTerminal.WriteLine()
        {
            this.WriteLine();
        }

        SharpMud.Terminal.TerminalColor SharpMud.Terminal.IOutputTerminal.BackgroundColor
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        SharpMud.Terminal.TerminalColor SharpMud.Terminal.IOutputTerminal.ForegroundColor
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        System.Text.Encoding SharpMud.Terminal.IOutputTerminal.OutputEncoding
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this._LatentConnectionReader.Dispose();
            this._Agents.Dispose();
            this._ParsedConnectionStream.Dispose();
            this._Connection.Dispose();
            
            _Dispose();   
        }
        protected virtual void _Dispose()
        {

        }

        #endregion

        #region IHostedConnectionHandler Members

        /// <summary>
        /// This method initializes the Telnet Session, using the given connection that
        /// the session is in charge of.
        /// </summary>
        /// <param name="connection">The network connection that this session is in charge of.</param>
        /// <param name="host">The network system that is hosting this TelnetSession</param>
        public void Initialize(SharpMud.Net.INetworkConnection connection, SharpMud.Server.Systems.Net.INetworkSystem host)
        {
            if (this._Connection != null)
                throw new InvalidOperationException("The TelnetSession has already been initialized!");

            this._Host = host;
            this._Connection = connection;
            this._Connection.Accept();

            this._SetupLayers();

#if LOG_IAC
            this.IAC.In.IAC_Feature_Negotiation += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventHandler(In_IAC_Feature_Negotiation);
            this.IAC.In.IAC_Other += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventHandler(In_IAC_Other);
            this.IAC.In.IAC_Subrequest += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACInputEventHandler(In_IAC_Subrequest);

            this.IAC.Out.IAC_Feature_Negotiation += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventHandler(Out_IAC_Feature_Negotiation);
            this.IAC.Out.IAC_Other += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventHandler(Out_IAC_Other);
            this.IAC.Out.IAC_Subrequest += new SharpMud.Net.Telnet.Session.DataProcessors.IAC.IACOutputEventHandler(Out_IAC_Subrequest);
#endif

            this._Agents = _SetupAgents();
            this._SetupPreferenceDefaults();

            Log.Info("CREATED "
                + "[localhost]:" + this.Connection.LocalEndpoint.Port.ToString() + "|"
                + "[" + this.Connection.RemoteEndpoint.Address.ToString() + "]:" + this.Connection.RemoteEndpoint.Port.ToString()
                );
#if TELNETSESSION__SELFPULSING
            Log.Info("STARTING pulsar thread.");

            this._Thread = new System.Threading.Thread(new System.Threading.ThreadStart(WorkThreadStart));
            this._ThreadShouldBeAlive = true;
            this._Thread.IsBackground = true;
            this._Thread.Start();
#endif

            this.Connection.Closing += new SharpMud.Net.ConnectionEventHandler(Connection_Closing);
            this.Connection.Disconnected += new SharpMud.Net.ConnectionEventHandler(Connection_Disconnected);
            this.Connection.LinkLost += new SharpMud.Net.ConnectionEventHandler(Connection_LinkLost);

            _Initialize();
        }



        public SharpMud.Server.Systems.Net.INetworkSystem Host
        {
            get { return _Host; }
        }

        #endregion
    }
}
